package com.umeox.moto.watch.themes.widget;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.umeox.moto.watch.themes.R;
import com.umeox.moto.watch.themes.app.ThemeConfig;
import com.umeox.moto.watch.themes.app.ThemeManager;
import com.umeox.moto.watch.themes.drawable.RippleDrawable;
import com.umeox.moto.watch.themes.util.MetricsUtils;
import com.umeox.moto.watch.themes.util.ThemeUtils;


public class Slider extends View implements ThemeManager.OnThemeChangedListener {
    private static final int HORIZONTAL = 0;
    private static final int VERTICAL = 1;

    private RippleManager mRippleManager;
    private int mCurrentStyle = ThemeManager.THEME_UNDEFINED;

    private Paint mPaint;
    private RectF mDrawRect;
    private RectF mTempRect;
    private Path mPerimaryTrackPath;
    private Path mSecondaryTrackPath;

    private int mMinValue = 0;
    private int mMaxValue = 100;

    private ColorStateList mPrimaryColor;
    private ColorStateList mSecondaryColor;
    private int mCurPrimaryColor, mCurSecondaryColor;
    private int mTrackSize = -1;

    private int mThumbRadius = -1;
    private float mThumbPosition = -1;

    private int mGravity = Gravity.CENTER;
    private int mTravelAnimationDuration = -1;
    private Interpolator mInterpolator;
    private int mBaselineOffset;

    private int mTouchSlop;
    private PointF mMemoPoint;
    private boolean mIsDragging;

    private ThumbMoveAnimator mThumbMoveAnimator;

    private int orientation = HORIZONTAL;
    private boolean mIsRtl = false;
    private OnPositionChangeListener mOnPositionChangeListener;
    private boolean mDrawableState;

    public Slider(Context context) {
        this(context, null);
    }

    public Slider(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.u_sliderStyle);
    }

    public Slider(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setClickable(true);
        setFocusable(true);
        setFocusableInTouchMode(true);
        init(context, attrs, defStyleAttr, 0);
    }

    protected void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        //default color
        setPrimaryColor(ThemeUtils.colorControlActivated(context, 0xFF000000));
        setSecondaryColor(ThemeUtils.colorControlActivated(context, 0xFF000000));

        mDrawRect = new RectF();
        mTempRect = new RectF();
        mPerimaryTrackPath = new Path();
        mSecondaryTrackPath = new Path();

        mThumbMoveAnimator = new ThumbMoveAnimator();

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mMemoPoint = new PointF();

        applyStyle(context, attrs, defStyleAttr, defStyleRes);

        if (!isInEditMode()) {
            mCurrentStyle = ThemeManager.getInstance().getCurrentStyle(context);
        }
    }

    public void applyStyle(int resId) {
        applyStyle(getContext(), null, 0, resId);
    }

    protected void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slider, defStyleAttr, defStyleRes);
        int minValue = getMinValue();
        int maxValue = getMaxValue();
        boolean valueRangeDefined = false;
        int value = -1;
        boolean valueDefined = false;

        for (int i = 0, count = a.getIndexCount(); i < count; i++) {
            int attr = a.getIndex(i);
            if (attr == R.styleable.Slider_sl_primaryColor) {
                setPrimaryColor(a.getColorStateList(attr));
            } else if (attr == R.styleable.Slider_sl_secondaryColor) {
                setSecondaryColor(a.getColorStateList(attr));
            } else if (attr == R.styleable.Slider_sl_trackSize) {
                mTrackSize = a.getDimensionPixelSize(attr, 0);
            } else if (attr == R.styleable.Slider_sl_thumbRadius) {
                mThumbRadius = a.getDimensionPixelSize(attr, 0);
            } else if (attr == R.styleable.Slider_sl_travelAnimDuration) {
                mTravelAnimationDuration = a.getInteger(attr, 0);
            } else if (attr == R.styleable.Slider_sl_interpolator) {
                int resId = a.getResourceId(R.styleable.Slider_sl_interpolator, 0);
                mInterpolator = AnimationUtils.loadInterpolator(context, resId);
            } else if (attr == R.styleable.Slider_sl_orientation) {
                orientation = a.getInteger(attr, HORIZONTAL);
            } else if (attr == R.styleable.Slider_android_gravity) {
                mGravity = a.getInteger(attr, 0);
            } else if (attr == R.styleable.Slider_sl_minValue) {
                minValue = a.getInteger(attr, 0);
                valueRangeDefined = true;
            } else if (attr == R.styleable.Slider_sl_maxValue) {
                maxValue = a.getInteger(attr, 0);
                valueRangeDefined = true;
            } else if (attr == R.styleable.Slider_sl_value) {
                value = a.getInteger(attr, 0);
                valueDefined = true;
            } else if (attr == R.styleable.Slider_android_enabled) {
                setEnabled(a.getBoolean(attr, true));
            } else if (attr == R.styleable.Slider_sl_baselineOffset) {
                mBaselineOffset = a.getDimensionPixelOffset(attr, 0);
            }
        }

        a.recycle();

        if (mTrackSize < 0) {
            mTrackSize = (int) MetricsUtils.convertDpToPixel(context, 2);
        }

        if (mThumbRadius < 0) {
            mThumbRadius = (int) MetricsUtils.convertDpToPixel(context, 10);
        }

        if (mTravelAnimationDuration < 0) {
            mTravelAnimationDuration = context.getResources().getInteger(android.R.integer.config_mediumAnimTime);
        }

        if (mInterpolator == null) {
            mInterpolator = new DecelerateInterpolator();
        }

        if (valueRangeDefined) {
            setValueRange(minValue, maxValue, false);
        }

        if (valueDefined) {
            setValue(value, false);
        } else if (mThumbPosition < 0) {
            setValue(mMinValue, false);
        }

        invalidate();
    }

    @Override
    public void onThemeChanged(@Nullable ThemeManager.OnThemeChangedEvent event) {
        int style = ThemeManager.getInstance().getCurrentStyle(getContext());
        if (mCurrentStyle != style) {
            mCurrentStyle = style;
            applyStyle(mCurrentStyle);
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mCurrentStyle != ThemeManager.THEME_UNDEFINED) {
            ThemeManager.getInstance().registerOnThemeChangedListener(this);
            onThemeChanged(null);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        RippleManager.cancelRipple(this);
        if (mCurrentStyle != ThemeManager.THEME_UNDEFINED) {
            ThemeManager.getInstance().unregisterOnThemeChangedListener(this);
        }
    }

    /**
     * @return The minimum selectable value.
     */
    public int getMinValue() {
        return mMinValue;
    }

    /**
     * @return The maximum selectable value.
     */
    public int getMaxValue() {
        return mMaxValue;
    }

    /**
     * Set the randge of selectable value.
     *
     * @param min       The minimum selectable value.
     * @param max       The maximum selectable value.
     * @param animation Indicate that should show animation when thumb's current position changed.
     */
    public void setValueRange(int min, int max, boolean animation) {
        if (max < min || (min == mMinValue && max == mMaxValue)) {
            return;
        }

        float oldValue = getExactValue();
        float oldPosition = getPosition();
        mMinValue = min;
        mMaxValue = max;

        setValue(oldValue, animation);
        if (mOnPositionChangeListener != null && oldPosition == getPosition() && oldValue != getExactValue()) {
            mOnPositionChangeListener.onPositionChanged(this, false, oldPosition, oldPosition, Math.round(oldValue), getValue());
        }
    }

    /**
     * @return The selected value.
     */
    public int getValue() {
        return Math.round(getExactValue());
    }

    /**
     * @return The exact selected value.
     */
    public float getExactValue() {
        return (mMaxValue - mMinValue) * getPosition() + mMinValue;
    }

    /**
     * @return The current position of thumb in [0..1] range.
     */
    public float getPosition() {
        return mThumbMoveAnimator.isRunning() ? mThumbMoveAnimator.getPosition() : mThumbPosition;
    }

    /**
     * Set current position of thumb.
     *
     * @param pos       The position in [0..1] range.
     * @param animation Indicate that should show animation when change thumb's position.
     */
    public void setPosition(float pos, boolean animation) {
        setPosition(pos, animation, animation, false);
    }

    private void setPosition(float pos, boolean moveAnimation, boolean transformAnimation, boolean fromUser) {
        boolean change = getPosition() != pos;
        int oldValue = getValue();
        float oldPos = getPosition();

        if (!moveAnimation || !mThumbMoveAnimator.startAnimation(pos)) {
            mThumbPosition = pos;

            if (transformAnimation) {
                invalidate();
            } else {
                invalidate();
            }
        }

        int newValue = getValue();
        float newPos = getPosition();

        if (change && mOnPositionChangeListener != null) {
            mOnPositionChangeListener.onPositionChanged(this, fromUser, oldPos, newPos, oldValue, newValue);
        }
    }

    public void onchangeState(boolean state) {
        mDrawableState = state;
    }

    /**
     * Changes the primary color and invalidates the view to force a redraw.
     *
     * @param color New color to assign to mPrimaryColor.
     */
    public void setPrimaryColor(int color) {
        mPrimaryColor = ColorStateList.valueOf(color);
        updateColors();
    }

    public void setPrimaryColor(ColorStateList color) {
        mPrimaryColor = color;
        updateColors();
    }

    /**
     * Changes the secondary color and invalidates the view to force a redraw.
     *
     * @param color New color to assign to mSecondaryColor.
     */
    public void setSecondaryColor(int color) {
        mSecondaryColor = ColorStateList.valueOf(color);
        updateColors();
    }

    public void setSecondaryColor(ColorStateList color) {
        mSecondaryColor = color;
        updateColors();
    }

    private void updateColors() {
        boolean inval = false;
        final int[] drawableState = getDrawableState();
        int color = mPrimaryColor.getColorForState(drawableState, 0);
        if (color != mCurPrimaryColor) {
            mCurPrimaryColor = color;
            inval = true;
        }
        if (mSecondaryColor != null) {
            color = mSecondaryColor.getColorForState(drawableState, 0);
            if (color != mCurSecondaryColor) {
                mCurSecondaryColor = color;
                inval = true;
            }
        }

        if (inval) {
            invalidate();
        }
    }

    /**
     * Set the selected value of this Slider.
     *
     * @param value     The selected value.
     * @param animation Indicate that should show animation when change thumb's position.
     */
    public void setValue(float value, boolean animation) {
        value = Math.min(mMaxValue, Math.max(value, mMinValue));
        setPosition((value - mMinValue) / (mMaxValue - mMinValue), animation);
    }

    /**
     * Set a listener will be called when thumb's position changed.
     *
     * @param listener The {@link OnPositionChangeListener} will be called.
     */
    public void setOnPositionChangeListener(OnPositionChangeListener listener) {
        mOnPositionChangeListener = listener;
    }

    @Override
    public void setBackgroundDrawable(Drawable drawable) {
        Drawable background = getBackground();
        if (background instanceof RippleDrawable && !(drawable instanceof RippleDrawable)) {
            ((RippleDrawable) background).setBackgroundDrawable(drawable);
        } else {
            super.setBackgroundDrawable(drawable);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        switch (widthMode) {
            case MeasureSpec.UNSPECIFIED:
                widthSize = getSuggestedMinimumWidth();
                break;
            case MeasureSpec.AT_MOST:
                widthSize = Math.min(widthSize, getSuggestedMinimumWidth());
                break;
        }

        switch (heightMode) {
            case MeasureSpec.UNSPECIFIED:
                heightSize = getSuggestedMinimumHeight();
                break;
            case MeasureSpec.AT_MOST:
                heightSize = Math.min(heightSize, getSuggestedMinimumHeight());
                break;
        }

        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    public int getSuggestedMinimumWidth() {
        return mThumbRadius * (orientation == HORIZONTAL ? 4 : 2) + getPaddingLeft() + getPaddingRight();
    }

    @Override
    public int getSuggestedMinimumHeight() {
        return mThumbRadius * (orientation == HORIZONTAL ? 2 : 4) + getPaddingTop() + getPaddingBottom();
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        boolean rtl = layoutDirection == LAYOUT_DIRECTION_RTL;
        if (mIsRtl != rtl) {
            mIsRtl = rtl;
            invalidate();
        }
    }

    @Override
    public int getBaseline() {
        int align = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        int baseline;
        int height = mThumbRadius * 2;
        switch (align) {
            case Gravity.TOP:
                baseline = getPaddingTop() + mThumbRadius;
                break;
            case Gravity.BOTTOM:
                baseline = getMeasuredHeight() - getPaddingBottom();
                break;
            default:
                baseline = Math.round((getMeasuredHeight() - height) / 2f + mThumbRadius);
                break;
        }
        return baseline + mBaselineOffset;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        int align = mGravity & (orientation == HORIZONTAL ? Gravity.VERTICAL_GRAVITY_MASK : Gravity.HORIZONTAL_GRAVITY_MASK);

        if (orientation == HORIZONTAL) {
            mDrawRect.left = getPaddingLeft();
            mDrawRect.right = w - getPaddingRight();

            int height = mThumbRadius * 2;

            switch (align) {
                case Gravity.TOP:
                    mDrawRect.top = getPaddingTop();
                    mDrawRect.bottom = mDrawRect.top + height;
                    break;
                case Gravity.BOTTOM:
                    mDrawRect.bottom = h - getPaddingBottom();
                    mDrawRect.top = mDrawRect.bottom - height;
                    break;
                default:
                    mDrawRect.top = (h - height) / 2f;
                    mDrawRect.bottom = mDrawRect.top + height;
                    break;
            }
        } else {
            mDrawRect.top = getPaddingTop();
            mDrawRect.bottom = h - getPaddingBottom();

            int width = mThumbRadius * 2;
            switch (align) {
                case Gravity.LEFT:
                    mDrawRect.left = getPaddingLeft();
                    mDrawRect.right = mDrawRect.left + width;
                    break;
                case Gravity.RIGHT:
                    mDrawRect.right = w - getPaddingRight();
                    mDrawRect.left = mDrawRect.right - width;
                    break;
                default:
                    mDrawRect.left = (w - width) / 2f;
                    mDrawRect.right = mDrawRect.left + width;
                    break;
            }
        }
    }

    private boolean isThumbHit(float x, float y, float radius) {
        float cx, cy;
        if (orientation == HORIZONTAL) {
            cx = mDrawRect.width() * mThumbPosition + mDrawRect.left;
            cy = mDrawRect.centerY();
        } else {
            cx = mDrawRect.centerX();
            cy = mDrawRect.bottom - mDrawRect.height() * mThumbPosition;
        }
        return x >= cx - radius && x <= cx + radius && y >= cy - radius && y < cy + radius;
    }

    private double distance(float x1, float y1, float x2, float y2) {
        return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        super.onTouchEvent(event);

        //getRippleManager().onTouchEvent(this, event);

        if (!isEnabled()) {
            return false;
        }

        float x = event.getX();
        float y = event.getY();
        if (mIsRtl) {
            x = 2 * mDrawRect.centerX() - x;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mIsDragging = isThumbHit(x, y, mThumbRadius) && !mThumbMoveAnimator.isRunning();
                mMemoPoint.set(x, y);
                if (mIsDragging) {
                    if (getParent() != null) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mIsDragging) {
                    float offset = orientation == HORIZONTAL ? (x - mMemoPoint.x) / mDrawRect.width() : (mMemoPoint.y - y) / mDrawRect.height();
                    float position = Math.min(1f, Math.max(0f, mThumbPosition + offset));
                    setPosition(position, false, true, true);
                    mMemoPoint.set(x, y);
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mIsDragging) {
                    mIsDragging = false;
                    setPosition(getPosition(), true, true, true);
                    if (getParent() != null) {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                } else if (distance(mMemoPoint.x, mMemoPoint.y, x, y) <= mTouchSlop) {
                    float offset = orientation == HORIZONTAL ? (x - mDrawRect.left) / mDrawRect.width() : (mDrawRect.bottom - y) / mDrawRect.height();
                    float position = Math.min(1f, Math.max(0f, offset));
                    setPosition(position, true, true, true);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsDragging) {
                    mIsDragging = false;
                    setPosition(getPosition(), true, true, true);

                    if (getParent() != null) {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }
                break;
        }

        return true;
    }

    private void getTrackPath(float x, float y) {
        float halfStroke = mTrackSize / 2f;

        mPerimaryTrackPath.reset();
        mSecondaryTrackPath.reset();

        if (orientation == HORIZONTAL) {
            if (x < mDrawRect.left) {
                x = mDrawRect.left;
            } else if (x > mDrawRect.right) {
                x = mDrawRect.right;
            }

            float leftAngle = 90, rightAngle = 90;
            float leftYOffset = halfStroke, rightYOffset = halfStroke;
            if (x <= mDrawRect.left + halfStroke) {
                leftAngle = (float) (Math.acos(Math.max(0f, (mDrawRect.left + halfStroke - x) / halfStroke)) / Math.PI * 180);
                leftYOffset = (float) Math.sqrt(Math.pow(halfStroke, 2) - Math.pow(mDrawRect.left + halfStroke - x, 2));
            } else if (x >= mDrawRect.right - halfStroke) {
                rightAngle = (float) (Math.acos(Math.max(0f, (x - mDrawRect.right + halfStroke) / halfStroke)) / Math.PI * 180);
                rightYOffset = (float) Math.sqrt(Math.pow(halfStroke, 2) - Math.pow(x + halfStroke - mDrawRect.right, 2));
            }
            Log.i("Slider", "leftAngle=" + leftAngle + ", leftYOffset=" + leftYOffset + ", rightAngle=" + rightAngle + ", rightYOffset=" + rightYOffset);

            mTempRect.set(mDrawRect.left, y - halfStroke, mDrawRect.left + mTrackSize, y + halfStroke);
            mPerimaryTrackPath.addArc(mTempRect, 180 - leftAngle, leftAngle * 2);

            mTempRect.set(mDrawRect.right - mTrackSize, y - halfStroke, mDrawRect.right, y + halfStroke);
            mSecondaryTrackPath.arcTo(mTempRect, 360 - rightAngle, rightAngle * 2);

            if (leftAngle < 90) {
                mPerimaryTrackPath.lineTo(x, y - leftYOffset);
                mPerimaryTrackPath.lineTo(x, y + leftYOffset);

                mSecondaryTrackPath.lineTo(mDrawRect.left + halfStroke, y + halfStroke);
                mTempRect.set(mDrawRect.left, y - halfStroke, mDrawRect.left + mTrackSize, y + halfStroke);
                mSecondaryTrackPath.arcTo(mTempRect, 90, 90 - leftAngle);
                mSecondaryTrackPath.lineTo(x, y - leftYOffset);
                mSecondaryTrackPath.addArc(mTempRect, 180 + leftAngle, 90 - leftAngle);
                mSecondaryTrackPath.lineTo(mDrawRect.right - halfStroke, y - halfStroke);
            } else if (rightAngle < 90) {
                mPerimaryTrackPath.lineTo(mDrawRect.right - halfStroke, y - halfStroke);
                mPerimaryTrackPath.lineTo(mDrawRect.right - halfStroke, y + halfStroke);
                mTempRect.set(mDrawRect.right - mTrackSize, y - halfStroke, mDrawRect.right, y + halfStroke);
                mPerimaryTrackPath.addArc(mTempRect, 270, 90 - rightAngle);
                mPerimaryTrackPath.lineTo(x, y + rightYOffset);
                mPerimaryTrackPath.addArc(mTempRect, rightAngle, 90 - rightAngle);
                mPerimaryTrackPath.lineTo(mDrawRect.right - halfStroke, y - halfStroke);

                mSecondaryTrackPath.lineTo(x, y - rightYOffset);
                mSecondaryTrackPath.lineTo(x, y + rightYOffset);
            } else {
                mPerimaryTrackPath.lineTo(x, y - halfStroke);
                mPerimaryTrackPath.lineTo(x, y + halfStroke);

                mSecondaryTrackPath.lineTo(x, y + halfStroke);
                mSecondaryTrackPath.lineTo(x, y - halfStroke);
            }
            mPerimaryTrackPath.close();
            mSecondaryTrackPath.close();
        } else {
            if (y > mDrawRect.bottom) {
                y = mDrawRect.bottom;
            } else if (y < mDrawRect.top) {
                y = mDrawRect.top;
            }

            float bottomAngle = 90, topAngle = 90;
            float bottomXOffset = halfStroke, topXOffset = halfStroke;
            if (y >= mDrawRect.bottom - halfStroke) {
                bottomAngle = (float) (Math.acos(Math.max(0f, (halfStroke - mDrawRect.bottom + y) / halfStroke)) / Math.PI * 180);
                bottomXOffset = (float) Math.sqrt(Math.pow(halfStroke, 2) - Math.pow(halfStroke - mDrawRect.bottom + y, 2));
            } else if (y <= mDrawRect.top + halfStroke) {
                topAngle = (float) (Math.acos(Math.max(0f, (halfStroke + mDrawRect.top - y) / halfStroke)) / Math.PI * 180);
                topXOffset = (float) Math.sqrt(Math.pow(halfStroke, 2) - Math.pow(halfStroke + mDrawRect.top - y, 2));
            }
            Log.i("Slider", "bottomAngle=" + bottomAngle + ", bottomXOffset=" + bottomXOffset + ", topAngle=" + topAngle + ", topXOffset=" + topXOffset);

            mTempRect.set(x - halfStroke, mDrawRect.bottom - mTrackSize, x + halfStroke, mDrawRect.bottom);
            mPerimaryTrackPath.arcTo(mTempRect, 90 - bottomAngle, bottomAngle * 2);

            mTempRect.set(x - halfStroke, mDrawRect.top, x + halfStroke, mDrawRect.top + mTrackSize);
            mSecondaryTrackPath.arcTo(mTempRect, 270 - topAngle, topAngle * 2);

            if (bottomAngle < 90) {
                mPerimaryTrackPath.lineTo(x - bottomXOffset, y);
                mPerimaryTrackPath.lineTo(x + bottomXOffset, y);

                mSecondaryTrackPath.lineTo(x + halfStroke, mDrawRect.bottom - halfStroke);
                mTempRect.set(x - halfStroke, mDrawRect.bottom - mTrackSize, x + halfStroke, mDrawRect.bottom);
                mSecondaryTrackPath.arcTo(mTempRect, 0, 90 - bottomAngle);
                mSecondaryTrackPath.lineTo(x - bottomXOffset, y);
                mSecondaryTrackPath.addArc(mTempRect, 90 + bottomAngle, 90 - bottomAngle);
                mSecondaryTrackPath.lineTo(x - halfStroke, mDrawRect.top + halfStroke);
            } else if (topAngle < 90) {
                mPerimaryTrackPath.lineTo(x - halfStroke, mDrawRect.top + halfStroke);
                mPerimaryTrackPath.lineTo(x + halfStroke, mDrawRect.top + halfStroke);
                mTempRect.set(x - halfStroke, mDrawRect.top, x + halfStroke, mDrawRect.top + mTrackSize);
                mPerimaryTrackPath.addArc(mTempRect, 180, 90 - topAngle);
                mPerimaryTrackPath.lineTo(x - topXOffset, y);
                mPerimaryTrackPath.lineTo(x + topXOffset, y);
                mPerimaryTrackPath.addArc(mTempRect, 270 + topAngle, 90 - topAngle);
                mPerimaryTrackPath.lineTo(x + halfStroke, mDrawRect.top + halfStroke);
                mPerimaryTrackPath.lineTo(x - halfStroke, mDrawRect.top + halfStroke);

                mSecondaryTrackPath.lineTo(x + topXOffset, y);
                mSecondaryTrackPath.lineTo(x - topXOffset, y);
            } else {
                mPerimaryTrackPath.lineTo(x - halfStroke, y);
                mPerimaryTrackPath.lineTo(x + halfStroke, y);

                mSecondaryTrackPath.lineTo(x + halfStroke, y);
                mSecondaryTrackPath.lineTo(x - halfStroke, y);
            }
            mPerimaryTrackPath.close();
            mSecondaryTrackPath.close();
        }
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        super.draw(canvas);
        float x, y;
        if (orientation == HORIZONTAL) {
            x = mDrawRect.width() * mThumbPosition + mDrawRect.left;
            if (mIsRtl) {
                x = 2 * mDrawRect.centerX() - x;
            }
            y = mDrawRect.centerY();
        } else {
            x = mDrawRect.centerX();
            y = mDrawRect.bottom - mDrawRect.height() * mThumbPosition;
        }

        getTrackPath(x, y);

        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mIsRtl ? mCurPrimaryColor : mCurSecondaryColor);
        canvas.drawPath(mSecondaryTrackPath, mPaint);
        mPaint.setColor(mIsRtl ? mCurSecondaryColor : mCurPrimaryColor);
        canvas.drawPath(mPerimaryTrackPath, mPaint);

        /*mPaint.setColor(mCurThumbColor);
        if (orientation == HORIZONTAL) {
            float left = mThumbRadius + mDrawRect.left;
            float right = mDrawRect.right - mThumbRadius;
            canvas.drawCircle(x <= left ? left : (x >= right ? right : x), y, mThumbRadius, mPaint);
        } else {
            float top = mThumbRadius + mDrawRect.top;
            float bottom = mDrawRect.bottom - mThumbRadius;
            canvas.drawCircle(x, y <= top ? top : (y >= bottom ? bottom : y), mThumbRadius, mPaint);
        }*/
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        if (!mDrawableState){
            return;
        }
        if (mPrimaryColor != null && mPrimaryColor.isStateful()
                || (mSecondaryColor != null && mSecondaryColor.isStateful())) {
            updateColors();
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();

        SavedState ss = new SavedState(superState);

        ss.position = getPosition();
        return ss;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;

        super.onRestoreInstanceState(ss.getSuperState());
        setPosition(ss.position, false);
        requestLayout();
    }

    /**
     * Interface definition for a callback to be invoked when thumb's position changed.
     */
    public interface OnPositionChangeListener {
        /**
         * Called when thumb's position changed.
         *
         * @param view     The view fire this event.
         * @param fromUser Indicate the change is from user touch event or not.
         * @param oldPos   The old position of thumb.
         * @param newPos   The new position of thumb.
         * @param oldValue The old value.
         * @param newValue The new value.
         */
        void onPositionChanged(Slider view, boolean fromUser, float oldPos, float newPos, int oldValue, int newValue);
    }

    static class SavedState extends BaseSavedState {
        public static final Creator<SavedState> CREATOR
                = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
        float position;

        /**
         * Constructor called from {@link Slider#onSaveInstanceState()}
         */
        SavedState(Parcelable superState) {
            super(superState);
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private SavedState(Parcel in) {
            super(in);
            position = in.readFloat();
        }

        @Override
        public void writeToParcel(@NonNull Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeFloat(position);
        }

        @Override
        public String toString() {
            return "Slider.SavedState{"
                    + Integer.toHexString(System.identityHashCode(this))
                    + " pos=" + position + "}";
        }
    }

    class ThumbMoveAnimator implements Runnable {

        boolean mRunning = false;
        long mStartTime;
        float mStartPosition;
        float mPosition;
        float mFillPercent;
        int mDuration;

        public boolean isRunning() {
            return mRunning;
        }

        public float getPosition() {
            return mPosition;
        }

        public void resetAnimation() {
            mStartTime = SystemClock.uptimeMillis();
            mStartPosition = mThumbPosition;
            mFillPercent = mPosition == 0 ? 0 : 1;
            mDuration = mTravelAnimationDuration;
        }

        public boolean startAnimation(float position) {
            if (mThumbPosition == position) {
                return false;
            }

            mPosition = position;

            if (getHandler() != null) {
                resetAnimation();
                mRunning = true;
                getHandler().postAtTime(this, SystemClock.uptimeMillis() + ThemeConfig.FRAME_DURATION);
                invalidate();
                return true;
            } else {
                mThumbPosition = position;
                invalidate();
                return false;
            }
        }

        public void stopAnimation() {
            mRunning = false;
            mThumbPosition = mPosition;
            if (getHandler() != null) {
                getHandler().removeCallbacks(this);
            }
            invalidate();
        }

        @Override
        public void run() {
            long curTime = SystemClock.uptimeMillis();
            float progress = Math.min(1f, (float) (curTime - mStartTime) / mDuration);
            float value = mInterpolator.getInterpolation(progress);

            mThumbPosition = (mPosition - mStartPosition) * value + mStartPosition;

            if (progress == 1f) {
                stopAnimation();
            }

            if (mRunning) {
                if (getHandler() != null) {
                    getHandler().postAtTime(this, SystemClock.uptimeMillis() + ThemeConfig.FRAME_DURATION);
                } else {
                    stopAnimation();
                }
            }

            invalidate();
        }

    }
}
